package cn.tradewin.advisor.services;

import java.io.IOException;
import java.io.Reader;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;

import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.tapestry5.ComponentParameterConstants;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.Translator;
import org.apache.tapestry5.Validator;
import org.apache.tapestry5.beaneditor.DataTypeConstants;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.ObjectLocator;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Contribute;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.ImportModule;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Marker;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.services.ApplicationGlobals;
import org.apache.tapestry5.services.BeanBlockContribution;
import org.apache.tapestry5.services.EditBlockContribution;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.Response;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.services.messages.ComponentMessagesSource;
import org.apache.tapestry5.upload.services.UploadSymbols;
import org.chenillekit.template.services.impl.FreeMarkerServiceImpl;
import org.slf4j.Logger;
import org.tynamo.activiti.services.ActivitiModule;
import org.tynamo.security.SecuritySymbols;
import org.tynamo.security.services.SecurityFilterChainFactory;
import org.tynamo.security.services.impl.SecurityFilterChain;

import cn.tradewin.advisor.anotations.Tradewin;
import cn.tradewin.advisor.commons.AdvisorSymbols;
import cn.tradewin.advisor.commons.util.DatepickerSymbols;
import cn.tradewin.advisor.mail.services.MailService;
import cn.tradewin.advisor.mail.services.impl.MailServiceImpl;
import cn.tradewin.advisor.persist.services.AccountService;
import cn.tradewin.advisor.persist.services.AdvisorSecurityService;
import cn.tradewin.advisor.persist.services.CalendarService;
import cn.tradewin.advisor.persist.services.CodetableService;
import cn.tradewin.advisor.persist.services.CodingChkService;
import cn.tradewin.advisor.persist.services.CustomerService;
import cn.tradewin.advisor.persist.services.DeptService;
import cn.tradewin.advisor.persist.services.JobService;
import cn.tradewin.advisor.persist.services.MTaskService;
import cn.tradewin.advisor.persist.services.PassetsMasterService;
import cn.tradewin.advisor.persist.services.ProjectService;
import cn.tradewin.advisor.persist.services.TestService;
import cn.tradewin.advisor.persist.services.VacationService;
import cn.tradewin.advisor.persist.services.WbsService;
import cn.tradewin.advisor.persist.services.impl.AccountServiceImpl;
import cn.tradewin.advisor.persist.services.impl.AdvisorSecurityServiceImpl;
import cn.tradewin.advisor.persist.services.impl.CalendarServiceImpl;
import cn.tradewin.advisor.persist.services.impl.CodetableServiceImpl;
import cn.tradewin.advisor.persist.services.impl.CodingChkServiceImpl;
import cn.tradewin.advisor.persist.services.impl.CustomerServiceImpl;
import cn.tradewin.advisor.persist.services.impl.DeptServiceImpl;
import cn.tradewin.advisor.persist.services.impl.JobServiceImpl;
import cn.tradewin.advisor.persist.services.impl.MTaskServiceImpl;
import cn.tradewin.advisor.persist.services.impl.PassetsMasterServiceImpl;
import cn.tradewin.advisor.persist.services.impl.ProjectServiceImpl;
import cn.tradewin.advisor.persist.services.impl.TestServiceImpl;
import cn.tradewin.advisor.persist.services.impl.VacationServiceImpl;
import cn.tradewin.advisor.persist.services.impl.WbsServiceImpl;
import cn.tradewin.advisor.security.matchers.RetryLimitHashedCredentialsMatcher;
import cn.tradewin.advisor.security.realms.AdvisorSecurityJdbcRealm;
import cn.tradewin.advisor.translators.CalendarTranslator;
import cn.tradewin.advisor.translators.DateTranslator;
import cn.tradewin.advisor.translators.TimestampTranslator;
import cn.tradewin.advisor.validators.AlphanumericValidator;
import freemarker.template.DefaultObjectWrapper;

/**
 * This module is automatically included as part of the Tapestry IoC Registry,
 * it's a good place to configure and extend Tapestry, or to place your own
 * service definitions.
 */

@ImportModule({ActivitiModule.class})
@Marker(Tradewin.class)
public final class AppModule {

	private final ApplicationGlobals globals;
	private final Logger log;
//	private final ObjectLocator objLocator;

	public AppModule(final ApplicationGlobals globals, final Logger log, final ObjectLocator objLocator) {
		this.globals = globals;
		this.log = log;
//		this.objLocator = objLocator;
	}

	/**
	 * get SqlSessionFactory by mybatis.
	 * 
	 * @param log
	 * @return
	 */
	@Scope(ScopeConstants.DEFAULT)
	@EagerLoad
	public SqlSessionFactory buildSqlSessionFactory() {
		SqlSessionFactory factory = null;
		try {
			String resource = "configuration.xml";
			Reader reader = Resources.getResourceAsReader(resource);
			factory = new SqlSessionFactoryBuilder().build(reader);
		} catch (Exception e) {
			log.error(e.getMessage());
			throw new RuntimeException(e);
		}
		return factory;
	}

	public void contributeFreeMarkerService(
			MappedConfiguration<String, freemarker.template.Configuration> configuration) {
		freemarker.template.Configuration fmConfiguration = new freemarker.template.Configuration();
		fmConfiguration.setWhitespaceStripping(false);
		fmConfiguration.setServletContextForTemplateLoading(globals.getServletContext(), "/");
		fmConfiguration.setObjectWrapper(new DefaultObjectWrapper());
		configuration.add(FreeMarkerServiceImpl.CONFIG_RESOURCE_KEY, fmConfiguration);
//		fmConfiguration.setClassForTemplateLoading(fmConfiguration.getClass(), "/");
	}

	public static void bind(ServiceBinder binder) {
		
		binder.bind(TestService.class, TestServiceImpl.class);
		
		binder.bind(AccountService.class, AccountServiceImpl.class);
		binder.bind(AdvisorSecurityService.class,
				AdvisorSecurityServiceImpl.class);
		binder.bind(VacationService.class, VacationServiceImpl.class);
		binder.bind(CodetableService.class, CodetableServiceImpl.class);
		binder.bind(CustomerService.class, CustomerServiceImpl.class);
		binder.bind(JobService.class, JobServiceImpl.class);
		binder.bind(ProjectService.class, ProjectServiceImpl.class);
		binder.bind(MTaskService.class, MTaskServiceImpl.class);
		binder.bind(WbsService.class, WbsServiceImpl.class);
		binder.bind(DeptService.class, DeptServiceImpl.class);
		binder.bind(CalendarService.class, CalendarServiceImpl.class);
		binder.bind(CodingChkService.class, CodingChkServiceImpl.class);
		binder.bind(PassetsMasterService.class, PassetsMasterServiceImpl.class);

		// shiro security area begin
		binder.bind(PasswordService.class, DefaultPasswordService.class);
		binder.bind(CredentialsMatcher.class,
				RetryLimitHashedCredentialsMatcher.class).withId(
				RetryLimitHashedCredentialsMatcher.class.getSimpleName());
		binder.bind(AuthorizingRealm.class, AdvisorSecurityJdbcRealm.class)
				.withId(AdvisorSecurityJdbcRealm.class.getSimpleName());
		// shiro security area end
		
		// mail service begin
		binder.bind(MailService.class, MailServiceImpl.class);
	}
	
	public static ProcessDiagramGenerator buildProcessDiagramGenerator() {
		return new DefaultProcessDiagramGenerator(1.0);
	}

	public static void contributeFactoryDefaults(
			MappedConfiguration<String, Object> configuration) {
		configuration.override(SymbolConstants.APPLICATION_VERSION,"1.0-SNAPSHOT");
		configuration.add(DatepickerSymbols.JQUERY_LIBRARY,"classpath:/META-INF/assets/datefield/jquery-ui.min.js");
    	configuration.add(DatepickerSymbols.JQUERY_CSS,"classpath:/META-INF/assets/datefield/jquery-ui.min.css");
	}

	public static void contributeApplicationDefaults(
			MappedConfiguration<String, Object> configuration) {

		// configuration.add(SymbolConstants.BOOTSTRAP_ROOT,
		// "classpath:/META-INF/assets/bootstrap-2.2.2");

		configuration.add(SymbolConstants.PRODUCTION_MODE, false);
		configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,ja,zh");
		configuration.add(SymbolConstants.COMBINE_SCRIPTS, false);
		configuration.add(SymbolConstants.MINIFICATION_ENABLED, false);
		configuration.add(SymbolConstants.HMAC_PASSPHRASE, "test");
		configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER,
				"jquery");
		configuration.add(ComponentParameterConstants.GRID_ROWS_PER_PAGE, "100");

		// for security add begin 2014/01/03 shiro
		configuration.add(SecuritySymbols.LOGIN_URL, "/login");
		configuration.add(SecuritySymbols.UNAUTHORIZED_URL, "/deny");
		configuration.add(SecuritySymbols.SUCCESS_URL, "/account/inbox");
		configuration.add(SecuritySymbols.REDIRECT_TO_SAVED_URL, "true");
		configuration.add(SymbolConstants.TAPESTRY_VERSION, "5.4-beta-26");
		configuration.add(AdvisorSymbols.DEFAULT_PASSWORD, "123456");
		configuration.add(AdvisorSymbols.MAIL_SESSION_JNDI, "mail/session");
		// for security add end 2014/01/03 shiro
		
		// add for file upload and download begin 2014/01/26
		configuration.add(UploadSymbols.FILESIZE_MAX, 10000000);
//		configuration.add(UploadSymbols.REPOSITORY_THRESHOLD, "");
//		configuration.add(UploadSymbols.REQUESTSIZE_MAX, "");
		configuration.add(UploadSymbols.REPOSITORY_LOCATION, "d:/downloads");
	}

//	/**
//	 * overwrite the SecurityFilterChainFactory provide by tapestry-security
//	 * 
//	 * @param cfg
//	 * @param advisorSfcf
//	 */
//	@Contribute(ServiceOverride.class)
//	public static void overrideSecurityFilterChainFactory(
//			MappedConfiguration<Class<?>, Object> cfg,
//			@Local SecurityFilterChainFactory advisorSfcf) {
//		cfg.add(SecurityFilterChainFactory.class, advisorSfcf);
//	}

	/**
	 * WebSecurityManager configuration information
	 * 
	 * @param configuration
	 * @param authorizingRealm
	 */
	@Contribute(WebSecurityManager.class)
	public static void addRealms(
			Configuration<Realm> configuration,
			@InjectService("AdvisorSecurityJdbcRealm") AuthorizingRealm jdbcRealm) {
		configuration.add(jdbcRealm);
	}

	/**
	 * configuration SecurityFilterChainFactory after overwrite it. define the
	 * constraint resource and access privilege.
	 * 
	 * @param configuration
	 * @param factory
	 */
	public static void contributeSecurityConfiguration(
			Configuration<SecurityFilterChain> configuration,
			SecurityFilterChainFactory factory) {
		// configuration.add(factory.createChain("/authc/signup").add(factory.anon()).build());
		// configuration.add(factory.createChain("/authc/**").add(factory.authc()).build());
		// configuration.add(factory.createChain("/contributed/**").add(factory.authc()).build());
		// configuration.add(factory.createChain("/user/signup").add(factory.anon()).build());
		// configuration.add(factory.createChain("/user/**").add(factory.user()).build());
		// configuration.add(factory.createChain("/perms/view/**").add(factory.perms(),"news:view").build());
		// configuration.add(factory.createChain("/perms/edit/**").add(factory.perms(),"news:edit").build());
		configuration.add(factory.createChain("/security/**")
				.add(factory.roles(), "admin").build());
		// configuration.add(factory.createChain("/security/**").add(factory.anon()).build());
	}

	/**
	 * This is a service definition, the service will be named "TimingFilter".
	 * The interface, RequestFilter, is used within the RequestHandler service
	 * pipeline, which is built from the RequestHandler service configuration.
	 * Tapestry IoC is responsible for passing in an appropriate Logger
	 * instance. Requests for static resources are handled at a higher level, so
	 * this filter will only be invoked for Tapestry related requests.
	 * <p/>
	 * <p/>
	 * Service builder methods are useful when the implementation is inline as
	 * an inner class (as here) or require some other kind of special
	 * initialization. In most cases, use the static bind() method instead.
	 * <p/>
	 * <p/>
	 * If this method was named "build", then the service id would be taken from
	 * the service interface and would be "RequestFilter". Since Tapestry
	 * already defines a service named "RequestFilter" we use an explicit
	 * service id that we can reference inside the contribution method.
	 */
	public RequestFilter buildTimingFilter(final Logger log) {
		return new RequestFilter() {
			public boolean service(Request request, Response response,
					RequestHandler handler) throws IOException {
				long startTime = System.currentTimeMillis();

				try {
					// The responsibility of a filter is to invoke the
					// corresponding method
					// in the handler. When you chain multiple filters together,
					// each filter
					// received a handler that is a bridge to the next filter.

					return handler.service(request, response);
				} finally {
					long elapsed = System.currentTimeMillis() - startTime;

					log.info(String.format("Request time: %d ms", elapsed));
				}
			}
		};
	}

	/**
	 * This is a contribution to the RequestHandler service configuration. This
	 * is how we extend Tapestry using the timing filter. A common use for this
	 * kind of filter is transaction management or security. The @Local
	 * annotation selects the desired service by type, but only from the same
	 * module. Without @Local, there would be an error due to the other
	 * service(s) that implement RequestFilter (defined in other modules).
	 */
	public void contributeRequestHandler(
			OrderedConfiguration<RequestFilter> configuration,
			@Local RequestFilter filter) {
		// Each contribution to an ordered configuration has a name, When
		// necessary, you may
		// set constraints to precisely control the invocation order of the
		// contributed filter
		// within the pipeline.

		configuration.add("Timing", filter);
	}
	
//	@Startup
//	public static void initAdvisorCache(Logger log) {
//		CacheManager.create(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
//	}
	
	
	public static void contributeFieldValidatorSource(MappedConfiguration<String, Validator> configuration, final JavaScriptSupport javaScriptSupport) {
		configuration.add("alphanumeric", new AlphanumericValidator(javaScriptSupport));
	}
	
	@Contribute(ComponentMessagesSource.class)
	public static void contributeComponentMessagesSource(
			@Value("/cn/tradewin/advisor/advisor.properties")Resource resource,
			OrderedConfiguration<Resource> configuration) {
		configuration.add("Messages", resource);
	}
	
	
	public static void contributeTranslatorSource(@SuppressWarnings("rawtypes") MappedConfiguration<Class, Translator> configuration){
		configuration.add(Date.class, 
				new DateTranslator("Date(yyyy/MM/dd)","yyyy/MM/dd","date-translator",
				"data-date-format","yy/MM/dd",
				"placeholder","yyyy/MM/dd"));
		configuration.add(Timestamp.class, 
				new TimestampTranslator("Timestamp(yyyy/MM/dd)","yyyy/MM/dd","timestamp-translator",
				"data-date-format","yy/MM/dd",
				"placeholder","yyyy/MM/dd"));
		configuration.add(Calendar.class, 
				new CalendarTranslator("Calendar(yyyy/MM/dd)","yyyy/MM/dd","calendar-translator",
				"data-date-format","yy/MM/dd",
				"placeholder","yyyy/MM/dd"));
	}
	
	public static void contributeTranslatorAlternatesSource(@SuppressWarnings("rawtypes") MappedConfiguration<String, Translator> configuration){
		Translator<?> translator = 
				new DateTranslator("Date(yyyy-MM-dd)","yyyy-MM-dd","date-translator",
				"data-date-format","yy-MM-dd",
				"placeholder","yyyy-MM-dd");
		configuration.add(translator.getName(), translator);
	}
	
//    public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration) {		
//    	configuration.add(new LibraryMapping("datefield", "cn.tradewin.advisor.components.Datepicker"));	
//    }
    
	public static void contributeBeanBlockOverrideSource(Configuration<BeanBlockContribution> configuration) {
		configuration.add(new EditBlockContribution(DataTypeConstants.DATE, "datefield/AppPropertyEditBlocks", "date"));
		configuration.add(new EditBlockContribution(DataTypeConstants.CALENDAR, "datefield/AppPropertyEditBlocks", "calendar"));
	}	
}
